#include <fs.h>



size_t ramdisk_read(void *buf, size_t offset, size_t len);
size_t ramdisk_write(const void *buf, size_t offset, size_t len);



enum {FD_STDIN, FD_STDOUT, FD_STDERR, FD_EVENTS, FD_FBCTL, FD_FB};

size_t invalid_read(void *buf, size_t offset, size_t len) {
  panic("should not reach here");
  return 0;
}

size_t invalid_write(const void *buf, size_t offset, size_t len) {
  panic("should not reach here");
  return 0;
}


size_t events_read(void *buf, size_t offset, size_t len);
size_t serial_write(const void *buf, size_t offset, size_t len);
size_t fb_write(const void *buf, size_t offset, size_t len);
size_t fbctl_write(const void *buf, size_t offset, size_t len);
size_t dispinfo_read(void *buf, size_t offset, size_t len);
/* This is the information about all files in disk. */
static Finfo file_table[] __attribute__((used)) = {
  [FD_STDIN]  = {"stdin", 0, 0, invalid_read, invalid_write},
  [FD_STDOUT] = {"stdout", -1, 0, invalid_read, serial_write},
  [FD_STDERR] = {"stderr", -1, 0, invalid_read, serial_write},
  [FD_EVENTS] = {"/dev/events",-1,0,events_read,invalid_write},
  [FD_FBCTL] = {"/dev/fbctl",-1,0,invalid_read,fbctl_write},
  [FD_FB] = {"/dev/fb",-1,0,invalid_read,fb_write},
  {"/proc/dispinfo",-1,0,dispinfo_read,invalid_write},
#include "files.h"
};

extern size_t fb_vmemsz;

void init_fs() {
  file_table[FD_FB].size = io_read(AM_GPU_CONFIG).vmemsz;
  fb_vmemsz = file_table[FD_FB].size;
  // TODO: initialize the size of /dev/fb
  
}
// calculate the length of an array
#ifndef ARRLEN
#define ARRLEN(arr) (int)(sizeof(arr) / sizeof(arr[0]))
#endif
static struct _OpenFinfo
{
  size_t offset;
} open_finfo[ARRLEN(file_table)];

#define FS_DEBUG_LOG 0
#if FS_DEBUG_LOG
#define FS_LOG(...) Log(__VA_ARGS__)
#else
#define FS_LOG(...) {}
#endif
char *fs_getname(int fd) {
  if(fd >= 0 && fd < ARRLEN(file_table)) return file_table[fd].name;
  return "# unknown #";
}
Finfo *fs_getinfo(int fd) {
  if(fd >= 0 && fd < ARRLEN(file_table)) return &file_table[fd];
  return NULL;
}
int fs_open(const char *pathname, int flags, int mode) {
  FS_LOG("open file : '%s'",pathname);
  for (int i = 0; i < ARRLEN(file_table); i++)
  {
    if(strcmp(file_table[i].name,pathname) != 0) continue;
    open_finfo[i].offset = 0;
    return i;
  }
  assert(0);
  return -1;
}
size_t fs_read(int fd, void *buf, size_t len) {
  FS_LOG("read file : '%s'",file_table[fd].name);
  if(fd <= FD_STDERR) return 0;
  assert(fd < ARRLEN(file_table));
  if(open_finfo[fd].offset >= file_table[fd].size) return 0;
  if(open_finfo[fd].offset+len > file_table[fd].size) {
    FS_LOG("fs_read(%d,%p,%d) is over the file '%s' space. File offset is %d",fd,buf,len,file_table[fd].name,open_finfo[fd].offset);
    
    len = file_table[fd].size-open_finfo[fd].offset;
  }
  size_t read_len = 0;
  if(file_table[fd].read != NULL) {
    read_len = file_table[fd].read(buf,file_table[fd].disk_offset + open_finfo[fd].offset,len);
  }
  else {
    read_len = ramdisk_read(buf,file_table[fd].disk_offset + open_finfo[fd].offset,len);
  }
  open_finfo[fd].offset += read_len;
  return read_len;
}
size_t fs_write(int fd, const void *buf, size_t len){
  FS_LOG("write file : '%s'",file_table[fd].name);
  assert(fd < ARRLEN(file_table) && fd >= 0);
  // const char *p = buf;
  switch (fd)
  {
  case FD_STDIN: return 0;
  // case FD_STDERR:
  // case FD_STDOUT:
  //   for (int i = 0; i < len; i++) putch(*p++);
  //   return len;
  //   break;
  default:
    if(open_finfo[fd].offset+len > file_table[fd].size) {
      FS_LOG("fs_write(%d,%p,%d) is over the file '%s' space",fd,buf,len,file_table[fd].name);
      len = file_table[fd].size-open_finfo[fd].offset;
    }
    size_t write_len = 0;
    if(file_table[fd].write != NULL) {
      write_len = file_table[fd].write(buf,file_table[fd].disk_offset + open_finfo[fd].offset,len);
    } 
    else {
      write_len = ramdisk_write(buf,file_table[fd].disk_offset + open_finfo[fd].offset,len);
    }
    open_finfo[fd].offset += write_len;
    return write_len;
    break;
  }
}
size_t fs_lseek(int fd, size_t offset, int whence){
  FS_LOG("lseek file : '%s' %d %d",file_table[fd].name,offset,whence);
  assert(fd < ARRLEN(file_table) && fd >= 0);
#if FS_DEBUG_LOG
  size_t old_offset = open_finfo[fd].offset;
#endif
  switch (whence)
  {
  case SEEK_SET:
    open_finfo[fd].offset = offset;
    break;
  case SEEK_CUR:
    open_finfo[fd].offset += offset;
    break;
  case SEEK_END:
    open_finfo[fd].offset = file_table[fd].size + offset;
    break;
  default:
    break;
  }
  FS_LOG("lseek %s offset form %d to %d",file_table[fd].name,old_offset,open_finfo[fd].offset);
  return open_finfo[fd].offset;
}
int fs_close(int fd) {
  FS_LOG("close file : '%s'",file_table[fd].name);
  return 0;
}